﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace DARE
{
    public class CHeightMapInfo
    {

    #region fields

        private float m_terrainScale = 1.0f;
        private float m_terrainBump = 25.0f;
        private float[,] m_heights;
        private Vector3 m_heightmapPosition;
        private float m_terrainWidth;
        private float m_terrainHeight;
        private Texture2D m_heightmap;
        private CNode m_ownerNode;
        private BoundingBox m_boundingBox;

    #endregion

    #region properties

        public float TerrainScale
        {
            get { return m_terrainScale; }
        }

        [ContentSerializerIgnore]
        public float[,] Heights
        {
            get { return m_heights; }
        }

        [ContentSerializerIgnore]
        public Texture2D Heightmap
        {
            get { return m_heightmap; }
        }

        public float TerrainWidth
        {
            get { return m_terrainWidth; }
        }

        public float TerrainHeight
        {
            get { return m_terrainHeight; }
        }

        public int Width
        {
            get { return m_heightmap.Width; }
        }

        public int Height
        {
            get { return m_heightmap.Height; }
        }

        public float TerrainBump
        {
            get { return m_terrainBump; }
        }

        [ContentSerializerIgnore]
        public CNode OwnerNode
        {
            get { return m_ownerNode; }
            internal set { m_ownerNode = value; }
        }

        public BoundingBox BoundingBox
        {
            get 
            { 
                return m_boundingBox; 
            }
        }

    #endregion

    #region ctor

        public CHeightMapInfo(float[,] heights, float terrainBump)
            :this(heights, terrainBump, 1.0f)
        {

        }

        public CHeightMapInfo(float[,] heights)
            :this(heights, 25.0f, 1.0f)
        {

        }

        public CHeightMapInfo(float[,] heights, float terrainBump, float terrainScale)
        {
            Texture2D texture = new Texture2D(CDare.Instance.GraphicsDevice, heights.GetLength(0), heights.GetLength(1));
            //texture.SetData(
            m_heightmap = texture;
            m_terrainBump = terrainBump;
            m_terrainScale = terrainScale;
            m_terrainWidth = heights.GetLength(0) * terrainScale;
            m_terrainHeight = heights.GetLength(1) * terrainScale;
            m_heightmapPosition.X = -m_terrainWidth / 2;
            m_heightmapPosition.Z = -m_terrainHeight / 2;
            m_heights = heights;
            BuildBoundingBox();
        }

        public CHeightMapInfo(Texture2D heightmap, float terrainBump)
            : this(heightmap, 25.0f, 1.0f)
        {

        }

        public CHeightMapInfo(Texture2D heightmap)
            : this(heightmap, 25.0f, 1.0f)
        {

        }

        public CHeightMapInfo(Texture2D heightmap, float terrainBump, float terrainScale)
        {
            m_heightmap = heightmap;
            m_terrainBump = terrainBump;
            m_terrainScale = terrainScale;
            m_terrainWidth = m_heightmap.Width * terrainScale;
            m_terrainHeight = m_heightmap.Height * terrainScale;
            m_heightmapPosition.X = -m_terrainWidth / 2;
            m_heightmapPosition.Z = -m_terrainHeight / 2;
            LoadHeightData();
            BuildBoundingBox();
        }

    #endregion

    #region methods

        private void LoadHeightData()
        {
            Color[] heightColor = new Color[Width * Height];
            m_heightmap.GetData(heightColor);
            float height; 

            m_heights = new float[Width, Height];
            for (int z = 0; z < Height; ++z)
                for (int x = 0; x < Width; ++x)
                    m_heights[x, z] = (heightColor[x + z * Width].R / 255.0f) * m_terrainBump;
        }

        private void BuildBoundingBox()
        {
            float height;

            //m_boundingBox = new BoundingBox(
                //new Vector3(-m_terrainWidth / 2 + 0.5f,
                    //float.MaxValue, -m_terrainHeight / 2 + 0.5f), 
                //new Vector3(m_terrainWidth / 2 - 0.5f,
                    //float.MinValue, m_terrainHeight / 2 - 0.5f));
            m_boundingBox = new BoundingBox(
                new Vector3(-m_terrainWidth / 2,
                    float.MaxValue, -m_terrainHeight / 2), 
                new Vector3(m_terrainWidth / 2,
                    float.MinValue, m_terrainHeight / 2));
            for (int z = 0; z < TerrainHeight; ++z)
                for (int x = 0; x < TerrainWidth; ++x)
                {
                    height = m_heights[x, z];
                    if (height > m_boundingBox.Max.Y)
                        m_boundingBox.Max.Y = height;
                    else if (height < m_boundingBox.Min.Y)
                        m_boundingBox.Min.Y = height;
                }
        }

        public bool IsOnHeightmap(Vector3 position)
        {
            Vector3 posOnHeightmap = position - m_heightmapPosition;

            return ((posOnHeightmap.X > 0) && (posOnHeightmap.X < m_terrainWidth) && 
                (posOnHeightmap.Z > 0) && (posOnHeightmap.Z < m_terrainHeight));
        }

        /// <summary>
        /// Return the height of the heightmap based on X and Z position.
        /// </summary>
        /// <param name="position">Position were you need to know the height, Y is not important as it will be known only after this call.</param>
        /// <returns>The height on the heightmap at this point..</returns>
        public float GetHeight(Vector3 position)
        {
            Vector3 posOnHeightmap = position - m_heightmapPosition;

            int left = (int)posOnHeightmap.X / (int)m_terrainScale;
            int top = (int)posOnHeightmap.Z / (int)m_terrainScale;

            float xNormalized = (posOnHeightmap.X % m_terrainScale) / m_terrainScale;
            float zNormalized = (posOnHeightmap.Z % m_terrainScale) / m_terrainScale;

            float topHeight = MathHelper.Lerp(m_heights[left, top], m_heights[left + 1, top], xNormalized);
            float bottomHeight = MathHelper.Lerp(m_heights[left, top + 1], m_heights[left + 1, top + 1], xNormalized);

            return MathHelper.Lerp(topHeight, bottomHeight, zNormalized);
        }

    #endregion

    }
}
